//=====Triangle.h=================================
#pragma once 

#include <string>

#include "GeometricObjects.h"
#include "TriangleVertex.h"
#include "ShadeRec.h"
#include "Material.h"
#include "Point3D.h"
#include "Point2D.h"
#include "ComplexMaterial.h"

using std::string;

namespace Engine
{
	class Triangle : public GeometricObjects
	{
	public:
		TriangleVertex point1;
		TriangleVertex point2;
		TriangleVertex point3;

		string m_materialName;
		GeometricObjects* m_parnetObject;

		Point3D minPoint;
		Point3D maxPoint;

		Triangle(TriangleVertex m_point1, TriangleVertex m_point2, TriangleVertex m_point3) : point1(m_point1), point2(m_point2), point3(m_point3)
		{
			m_parnetObject = NULL;
			MakeBoundingBox();
		}

		Triangle(TriangleVertex m_point1, TriangleVertex m_point2, TriangleVertex m_point3, Material* p_triangleMaterial, string p_materialName) : point1(m_point1), point2(m_point2), point3(m_point3)
		{
			m_materialName = p_materialName;
			m_material = p_triangleMaterial;
			m_parnetObject = NULL;
			/*if (m_material->GetMaterialName() != "kamen_zid")
			{
				cout << "error" << endl;
			}*/

			MakeBoundingBox();
		}

		//Triangle(TriangleVertex m_point1, TriangleVertex m_point2, TriangleVertex m_point3, Material* p_triangleMaterial, string p_materialName) :
		//	point1(m_point1), point2(m_point2), point3(m_point3)
		//{
		//	m_materialName = p_materialName;
		//	m_material = p_triangleMaterial;
		//	m_parnetObject = NULL;
		//	/*if (m_material->GetMaterialName() != "kamen_zid")
		//	{
		//	cout << "error" << endl;
		//	}*/

		//	MakeBoundingBox();
		//}

		Triangle(TriangleVertex m_point1, TriangleVertex m_point2, TriangleVertex m_point3, Material* p_triangleMaterial, string p_materialName, GeometricObjects* p_parentObject) : 
		point1(m_point1), point2(m_point2), point3(m_point3), m_parnetObject(p_parentObject)
		{
			m_materialName = p_materialName;
			m_material = p_triangleMaterial;
			/*if (m_material->GetMaterialName() != "kamen_zid")
			{
			cout << "error" << endl;
			}*/

			MakeBoundingBox();
		}

		#define __EPSILON 1e-5

		#define __CROSS(dest, v1, v2) \
			dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; \
			dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; \
			dest[2] = v1[0] * v2[1] - v1[1] * v2[0];

		#define __DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])

		#define __SUB(dest, v1, v2) \
			dest[0] = v1[0] - v2[0]; \
			dest[1] = v1[1] - v2[1]; \
			dest[2] = v1[2] - v2[2];

		#define __DET(c1, c2, c3) \
			(c1[0] * (c2[1] * c3[2] - c3[1] * c2[2]) - \
			c1[1] * (c2[0] * c3[2] - c3[0] * c2[2]) + \
			c1[2] * (c2[0] * c3[1] - c3[0] * c2[1]))


		Point2D CalcBarycentric(const Ray &p_ray, ShadeRec& p_sr)
		{
			//----------------------------------------------------------------------------------------------
			// Note that p_surface is not modified unless there has been an intersection
			//----------------------------------------------------------------------------------------------

			//----------------------------------------------------------------------------------------------
			// Shirley's 
			//----------------------------------------------------------------------------------------------
			float te1xte2[3],
				edge2[3],
				interm[3];

			Point3D p0 = point2.vertex - point1.vertex,
				p1 = point3.vertex - point1.vertex;

			float v0[3] = { (float)point1.vertex.x, (float)point1.vertex.y, (float)point1.vertex.z };

			float _te1[3] = { (float)p0.x, (float)p0.y, (float)p0.z },
				_te2[3] = { (float)p1.x, (float)p1.y, (float)p1.z };

			float _ro[3] = { (float)p_ray.o.x, (float)p_ray.o.y, (float)p_ray.o.z },
				_rd[3] = { (float)p_ray.d.x, (float)p_ray.d.y, (float)p_ray.d.z };

			const float *te1 = _te1;
			const float *te2 = _te2;

			__CROSS(te1xte2, te1, te2);

			const float rcp = 1.0f / __DOT(te1xte2, _rd);
			__SUB(edge2, v0, _ro);

			const float t = __DOT(te1xte2, edge2) * rcp;

			//if (t > p_ray.Max - 1e-5 || t < 1e-5)
			if (t < 1e-5) return false;

			__CROSS(interm, _rd, edge2);

			const float beta = __DOT(interm, te2) * -rcp;
			if (beta < 0.0f)
				return false;

			const float gamma = __DOT(interm, te1) * rcp;
			if (beta + gamma > 1.0f || gamma < 0.0f)
				return false;

			const float alpha = 1.f - beta - gamma;

			float u = point1.textureCoordinate.x * alpha +
				point2.textureCoordinate.x * beta +
				point3.textureCoordinate.x * gamma;

			float v = point1.textureCoordinate.y * alpha +
				point2.textureCoordinate.y * beta +
				point3.textureCoordinate.y * gamma;

			p_sr.tmin = t;
			p_sr.depth = t;
			p_sr.localHitPoint = (Point3D)p_ray.o + (t * (Point3D)p_ray.d);
			p_sr.hitAnObject = true;
			p_sr.textureCoordinates.x = u;
			p_sr.textureCoordinates.y = v;
			p_sr.m_hitTriangle = this;
			p_sr.normal = p0.CrossProduct(p1).normalize();
			
			Point2D barycentricCoor(u, v);
			return barycentricCoor;

			//p_sr.normal.normalize();
			//p_sr.normal.x = point1.normal.x * alpha + point2.normal.x * beta + point3.normal.x * gamma;
			//p_sr.normal.y = point1.normal.y * alpha + point2.normal.y * beta + point3.normal.y * gamma;
			//p_sr.normal.z = point1.normal.z * alpha + point2.normal.z * beta + point3.normal.z * gamma;

			//const Vertex &v0 = m_pMesh->VertexList[m_nVertexID[0]];
			//const Vertex &v1 = m_pMesh->VertexList[m_nVertexID[1]];
			//const Vertex &v2 = m_pMesh->VertexList[m_nVertexID[2]];

			// Populate differential surface
			//p_surface.SetShape(this);
			//p_surface.Distance = t;
			//p_surface.Point = p_ray.PointAlongRay(t);

			// Set 2d parametric surface representation
			//p_surface.PointUV.Set(
			//	v0.UV.U * alpha + v1.UV.U * beta + v2.UV.U * gamma,
			//	v0.UV.V * alpha + v1.UV.V * beta + v2.UV.V * gamma);

			// Set shading normal
			//p_surface.ShadingNormal.Set(v0.Normal.X * alpha + v1.Normal.X * beta + v2.Normal.X * gamma,
			//	v0.Normal.Y * alpha + v1.Normal.Y * beta + v2.Normal.Y * gamma,
			//	v0.Normal.Z * alpha + v1.Normal.Z * beta + v2.Normal.Z * gamma);

			//p_surface.ShadingNormal.Normalize();

			//p_surface.GeometryNormal.Set(te1xte2[0], te1xte2[1], te1xte2[2]);
			//p_surface.GeometryNormal.Normalize();

			// p_surface.GeometryNormal = p_surface.ShadingNormal;

			/* Sanity check */
			/*
			if (p_surface.ShadingNormal.X != p_surface.ShadingNormal.X)
			std::cerr << "Warning : Indeterminate normal computation!" << std::endl;
			*/

			//return true;
		}

		//keiths method to re review
		bool IntersectsNew(const Ray &p_ray, ShadeRec& p_sr)
		{
			//----------------------------------------------------------------------------------------------
			// Note that p_surface is not modified unless there has been an intersection
			//----------------------------------------------------------------------------------------------

			//----------------------------------------------------------------------------------------------
			// Shirley's 
			//----------------------------------------------------------------------------------------------
			float te1xte2[3],
				edge2[3],
				interm[3];

			Point3D p0 = point2.vertex - point1.vertex,
				p1 = point3.vertex - point1.vertex;

			float v0[3] = { (float)point1.vertex.x, (float)point1.vertex.y, (float)point1.vertex.z };

			float _te1[3] = { (float)p0.x, (float)p0.y, (float)p0.z },
				_te2[3] = { (float)p1.x, (float)p1.y, (float)p1.z };

			float _ro[3] = { (float)p_ray.o.x, (float)p_ray.o.y, (float)p_ray.o.z },
				_rd[3] = { (float)p_ray.d.x, (float)p_ray.d.y, (float)p_ray.d.z };

			const float *te1 = _te1;
			const float *te2 = _te2;

			__CROSS(te1xte2, te1, te2);

			const float rcp = 1.0f / __DOT(te1xte2, _rd);
			__SUB(edge2, v0, _ro);

			const float t = __DOT(te1xte2, edge2) * rcp;

			//if (t > p_ray.Max - 1e-5 || t < 1e-5)
			if (t < 1e-5) return false;

			__CROSS(interm, _rd, edge2);

			const float beta = __DOT(interm, te2) * -rcp;
			if (beta < 0.0f)
				return false;

			const float gamma = __DOT(interm, te1) * rcp;
			if (beta + gamma > 1.0f || gamma < 0.0f)
				return false;

			const float alpha = 1.f - beta - gamma;

			float u = point1.textureCoordinate.x * alpha +
				point2.textureCoordinate.x * beta +
				point3.textureCoordinate.x * gamma;

			float v = point1.textureCoordinate.y * alpha +
				point2.textureCoordinate.y * beta +
				point3.textureCoordinate.y * gamma;

			p_sr.tmin = t;
			p_sr.depth = t;
			p_sr.localHitPoint = (Point3D)p_ray.o + (t * (Point3D)p_ray.d);
			p_sr.hitAnObject = true;
			p_sr.textureCoordinates.x = u;
			p_sr.textureCoordinates.y = v;
			p_sr.m_hitTriangle = this;
			p_sr.normal = p0.CrossProduct(p1).normalize();
			//p_sr.normal.normalize();
			//p_sr.normal.x = point1.normal.x * alpha + point2.normal.x * beta + point3.normal.x * gamma;
			//p_sr.normal.y = point1.normal.y * alpha + point2.normal.y * beta + point3.normal.y * gamma;
			//p_sr.normal.z = point1.normal.z * alpha + point2.normal.z * beta + point3.normal.z * gamma;

			//const Vertex &v0 = m_pMesh->VertexList[m_nVertexID[0]];
			//const Vertex &v1 = m_pMesh->VertexList[m_nVertexID[1]];
			//const Vertex &v2 = m_pMesh->VertexList[m_nVertexID[2]];

			// Populate differential surface
			//p_surface.SetShape(this);
			//p_surface.Distance = t;
			//p_surface.Point = p_ray.PointAlongRay(t);

			// Set 2d parametric surface representation
			//p_surface.PointUV.Set(
			//	v0.UV.U * alpha + v1.UV.U * beta + v2.UV.U * gamma,
			//	v0.UV.V * alpha + v1.UV.V * beta + v2.UV.V * gamma);

			// Set shading normal
			//p_surface.ShadingNormal.Set(v0.Normal.X * alpha + v1.Normal.X * beta + v2.Normal.X * gamma,
			//	v0.Normal.Y * alpha + v1.Normal.Y * beta + v2.Normal.Y * gamma,
			//	v0.Normal.Z * alpha + v1.Normal.Z * beta + v2.Normal.Z * gamma);

			//p_surface.ShadingNormal.Normalize();

			//p_surface.GeometryNormal.Set(te1xte2[0], te1xte2[1], te1xte2[2]);
			//p_surface.GeometryNormal.Normalize();

			// p_surface.GeometryNormal = p_surface.ShadingNormal;

			/* Sanity check */
			/*
			if (p_surface.ShadingNormal.X != p_surface.ShadingNormal.X)
			std::cerr << "Warning : Indeterminate normal computation!" << std::endl;
			*/

			return true;
		}




#define CROSS(rez,v1,v2) \
	rez.x = v1.y*v2.z - v1.z*v2.y; \
	rez.y = v1.z*v2.x - v1.x*v2.z; \
	rez.z = v1.x*v2.y - v1.y*v2.x;

		using GeometricObjects::hit;
		bool hit(Point3D o, Point3D d, double &tmin, ShadeRec &sr) 
		{
			Point3D e1, e2, q, r, s;

			e1 = point2.vertex - point1.vertex;
			e2 = point3.vertex - point1.vertex;

			q = d.CrossProduct(e2);

			d.normalize();
			//NORMALIZE(pvec);
			float a = DOT(q, e1);
#ifdef TEST_CULL
			if (det <EPSILON)
			{
				return false;
			}
			SUB(tvec, orig, v1);
			float u = DOT(tvec, pvec);
			if (u < 0.0 || u > det)
			{

				return false;
			}
			CROSS(qvec, tvec, e1);
			float v = DOT(dir, qvec);
			if (v < 0.0f || v + u > det)
			{

				return false;
			}
#else
			if (a < kEpsilon && a > -kEpsilon)
			{
				return false;
			}

			float f = 1.0f / a;
			s = o - point1.vertex;
			// NORMALIZE(tvec);
			float u = f * DOT(s, q);
			if (u <0.0f || u > 1.0f)
			{

				return false;
			}

			r = s.CrossProduct(e1);

			// NORMALIZE(qvec);
			float v = f * DOT(r, d);
			if (v < 0.0f || u + v > 1.0f)
			{

				return false;
			}
#endif
			float t = f * e2.dot(r);

			if (t < tmin)
			{
				tmin = t;
				sr.localHitPoint = o + d * tmin;
				sr.normal = e1.CrossProduct(e2);
				sr.normal.normalize();
				sr.tmin = tmin;
				sr.m_hitTriangle = this;
			}

			return true;
		}

		Point3D TakeCenterPoint(void)
		{
			return (point1.vertex + point2.vertex + point3.vertex) / 3;
		}
		
		using GeometricObjects::GetNormal;
		Point3D GetNormal(Point3D p_hitPoint)
		{
			Point3D e1 = point2.vertex - point1.vertex;
			Point3D e2 = point3.vertex - point1.vertex;

			return e1.CrossProduct(e2);
		}

		using GeometricObjects::GetBoundingBox;
		Engine::BoundingBox* GetBoundingBox(void)
		{
			return box;
		}

		void MakeBoundingBox(void)
		{
			BoundingBox *bbox = new BoundingBox();
			bbox->Expand(point1.vertex);
			bbox->Expand(point2.vertex);
			bbox->Expand(point3.vertex);
			box = bbox;
		}

		using GeometricObjects::GetMaterial;
		Material* GetMaterial()
		{
			if (m_parnetObject == NULL)
			{
				return m_material;
			}
			else
			{
				ComplexMaterial* complexMaterial = (ComplexMaterial*)m_parnetObject->GetMaterial();
				return complexMaterial->GetMaterialFromMap(m_materialName);
			}
		}

		Point3D GetBarycentricCoordinates(Point3D& p_collisionPoint, Point3D& p_normal, Ray& p_ray)
		{
			#pragma region bad barycentric functions
				/*double beta = 0, gamma = 0;
				Point3D q = p_collisionPoint - point1.vertex;

				Point3D E1 = point2.vertex - point1.vertex;
				Point3D E2 = point3.vertex - point1.vertex;

				beta = (E2.CrossProduct(q).dot(p_ray.d)) / p_ray.d.dot(p_normal);
				gamma = (E1.CrossProduct(q).dot(p_ray.d)) / (p_ray.d.dot(p_normal));

				Point2D output(beta, gamma);
				return output;

				double beta = 0, gamma = 0;
				Point3D q = p_collisionPoint - point1.vertex;

				Point3D E1 = point2.vertex - point1.vertex;
				Point3D E2 = point3.vertex - point1.vertex;

				Point3D v2 = p_collisionPoint - point1.vertex;

				float d00 = point1.vertex.dot(point1.vertex);
				float d01 = point1.vertex.dot(point2.vertex);
				float d11 = point2.vertex.dot(point2.vertex);
				float d20 = point3.vertex.dot(point1.vertex);
				float d21 = point3.vertex.dot(point2.vertex);

				float denom = d00 * d11 - d01 * d01;

				beta = (d11 * d20 - d01 * d21) / denom;
				gamma = (d00 * d21 - d01 * d20) / denom;

				if (gamma + beta > 1)
				{
					cout << "clearly an error" << endl;
				}

				Point2D output(beta, gamma);
				return output;*/

			#pragma endregion 

			Point3D baryCoor(0);
						////Point3D q = p_collisionPoint - point1.vertex;
			//Point3D u = point2.vertex - point1.vertex;
			//Point3D v = point3.vertex - point1.vertex;
			//Point3D w = p_collisionPoint - point1.vertex;
			//float a = 0.5 * u.CrossProduct(w).length();
			//float b = 0.5 * v.CrossProduct(w).length();
			////total area of the triangle 
			//float c = 0.5 * u.CrossProduct(v).length();
			////beta
			//double s = b / c;
			////alpha
			//double t = a / c;

			Point3D u = point2.vertex - point1.vertex;
			Point3D v = point3.vertex - point1.vertex;
			Point3D w = p_collisionPoint - point1.vertex;

			float trinagleArea = 0.5 * (u.CrossProduct(v).length());
			float areaA = 0.5 * (u.CrossProduct(w).length());
			float areaB = 0.5 * (v.CrossProduct(w).length());
			float areaC = trinagleArea - areaA - areaB;

			baryCoor.x = areaA / trinagleArea;
			baryCoor.y = areaB / trinagleArea;
			baryCoor.z = areaC / trinagleArea;

			//Point3D n = (point2.vertex - point1.vertex).CrossProduct((point3.vertex - point1.vertex));
			//Point3D nA = (point3.vertex - point2.vertex).CrossProduct((p_collisionPoint - point2.vertex));
			//Point3D nB = (point1.vertex - point3.vertex).CrossProduct((p_collisionPoint - point3.vertex));
			//Point3D nC = (point2.vertex - point1.vertex).CrossProduct(p_collisionPoint - point1.vertex);
			//
			//float insideTriangle = (n.dot(nA)) / (n.length() * nA.length());
			//Point3D output(0);
			////if true point inside triangle
			//if (insideTriangle == 1)
			//{
			//	float wholeArea = n.length();
			//	float a = nA.length();
			//	float b = nB.length();
			//	float c = nC.length();
			//	//beta
			//	//output.y = c / 
			//	//alpha
			//	float nLength = n.length();

			//	output.x = n.dot(nA)/(nLength*nLength);
			//	output.y = n.dot(nB)/(nLength * nLength);
			//	output.z = n.dot(nC)/(nLength*nLength);
			//}

			//float total = output.x + output.y + output.z;
			
			return baryCoor;
			
		}

		virtual void PrintObjectName(void)
		{
			cout << "Triangle " << m_material->GetMaterialName() << "---------" << endl;
		}
	};
}

//======Triangle.h=================================
